package hk.ces.oxfam.patch;

import hk.ces.oxfam.patch.connection.ConnectionFactory;
import hk.ces.oxfam.patch.db.result.ExcuteStatus;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;

/**
 *
 * @author ReutyCheng
 */
public class TransactionEngine
{
  private final Set<TransactionPatch> patchs = new LinkedHashSet<TransactionPatch>();
  private final Map<Class, Boolean> patchsMap = new HashMap<Class, Boolean>();

  public TransactionEngine()
  {
  }

  public void add(TransactionPatch patch)
  {
    patchs.add(patch);
    patchsMap.put(patch.getClass(), Boolean.FALSE);
  }

  public void runAll()
  {
    Connection connection = ConnectionFactory.openConnection();
    for (TransactionPatch patch : patchs)
    {
      Logger.getLogger(this.getClass()).debug("====================================================================================================================================");
      Logger.getLogger(this.getClass()).debug("Ready to run Patch [" + patch.getClass() + "]");
      if (!patchsMap.containsKey(patch.getClass()))
      {
        Logger.getLogger(this.getClass()).error("TransactionEngine doesn't exist Patch [" + patch.getClass() + "]. Ignore this patch");
        continue;
      }
      if (patchsMap.get(patch.getClass()))
      {
        Logger.getLogger(this.getClass()).error("Patch [" + patch.getClass() + "] have run. Ignore this patch");
        continue;
      }

      try
      {
        TransactionStrategyType strategyType = this.getStrategyType(patch);
        switch (strategyType)
        {
          case COMMIT_IN_EACH_STATEMENT:
            connection.setAutoCommit(true);
            break;
          case COMMIT_IN_ENGINE:
          case ROLLBACK:
          default:
            connection.setAutoCommit(false);
            break;
        }

        Logger.getLogger(this.getClass()).debug("Start run Patch [" + patch.getClass() + "]. Transaction Strategy is [" + strategyType + "]");
        Logger.getLogger(this.getClass()).debug("====================================================================================================================================");
        patch.run(connection);

        ExcuteStatus excuteStatus = null;
        switch (strategyType)
        {
          case COMMIT_IN_EACH_STATEMENT:
          case COMMIT_IN_ENGINE:
            excuteStatus = this.commit(connection);
            break;
          case ROLLBACK:
          default:
            excuteStatus = this.rollback(connection);
            break;
        }

        Logger.getLogger(this.getClass()).debug("End Patch [" + patch.getClass() + "]. Excute Status is [" + excuteStatus + "]");
        Logger.getLogger(this.getClass()).debug("====================================================================================================================================");
        Logger.getLogger(this.getClass()).debug("");
      }
      catch (SQLException exception)
      {
        Logger.getLogger(this.getClass()).error(exception.getMessage(), exception);
      }
    }
  }

  private TransactionStrategyType getStrategyType(TransactionPatch patch)
  {
    TransactionStrategy strategy = patch.getClass().getAnnotation(TransactionStrategy.class);
    if (strategy != null)
    {
      TransactionStrategyType strategyType = strategy.value();
      return strategyType != null ? strategyType : TransactionStrategyType.ROLLBACK;
    }

    return TransactionStrategyType.ROLLBACK;
  }

  private ExcuteStatus commit(Connection connection)
  {
    try
    {
      connection.commit();
      return ExcuteStatus.COMMIT;
    }
    catch (SQLException ex)
    {
      Logger.getLogger(ConnectionFactory.class.getName()).error(ex.getMessage(), ex);
      return rollback(connection);
    }
  }

  private ExcuteStatus rollback(Connection connection)
  {
    try
    {
      connection.rollback();
      return ExcuteStatus.ROLLBACK;
    }
    catch (SQLException ex)
    {
      Logger.getLogger(ConnectionFactory.class.getName()).error(ex.getMessage(), ex);
      return ExcuteStatus.EXCEPTION;
    }
  }
}
